#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <elf.h>

typedef struct elf_info_t_tag
{
    char   *file_name;
    int    class;
    int    type;
    size_t entry_point;
    size_t program_header_table_offset;
    size_t program_header_count;
    size_t section_header_table_offset;
    size_t section_header_count;
    int    section_name_table_index;
    char   *section_name_table;
    size_t section_name_table_size;
} elf_info_t;

typedef struct elf_section_info_t_tag
{
    size_t name_index;
    size_t offset;
    size_t size;
    size_t address;
} elf_section_info_t;

void hex_dump(const void *buff, const int count);

static void usage(const char *name);
static void parse_args(int argc, char *argv[]);
static void print_error(const char *format, ...);
static int process_file(FILE *fp);
static int read_elf_header(FILE *fp);
static int read_section_header(FILE *fp);
static int read_section_name_table(FILE *fp);
static int print_info(FILE *fp);
static int dump_section(FILE *fp, const size_t section_num);
static const char *describe_elf_type(const int type);
static const char *describe_elf_class(const int class);

static int do_section_dump;
static size_t argv_section_dump_num;

static elf_info_t elf_info;
static elf_section_info_t *elf_section_info_table;

#define MALLOC_ERROR()                                      \
    print_error("alloc memory failed: %s", strerror(errno)) \

#define FREAD_ERROR()                                    \
    print_error("read file failed: %s", strerror(errno)) \

#define FSEEK_ERROR()                                    \
    print_error("seek file failed: %s", strerror(errno)) \

int main(int argc, char *argv[])
{
    FILE *fp = NULL;

    parse_args(argc, argv);

    if (NULL == (fp = fopen(elf_info.file_name, "rb")))
    {
        print_error("open file %s failed: %s\n",
                    elf_info.file_name, strerror(errno)); 
        exit(EXIT_FAILURE);
    }

    if (process_file(fp) != 0)
    {
        fclose(fp);
        exit(EXIT_FAILURE);
    }

    fclose(fp);
    exit(EXIT_SUCCESS);
}

static void usage(const char *name)
{
    print_error("usage: %s [option] <file_name>\n", name);
    print_error("    -s <section num> hexdump section\n");
    print_error("    -h               get help\n");
}

static void parse_args(int argc, char *argv[])
{
    int c;

    if (argc < 2)
    {
        usage(argv[0]);
        exit(EXIT_FAILURE);
    }
    
    while((c = getopt(argc, argv, "s:h")) != EOF)
    {
        switch (c)
        {
        case 's':
            ++do_section_dump;
            argv_section_dump_num = atoi(optarg);
            break;

        case 'h':
            usage(argv[0]);
            exit(EXIT_SUCCESS);
            break;

        default:
            print_error("invalid option '-%c'\n", c);
            /* drop though */
        case '?':
            usage(argv[0]);
            exit(EXIT_FAILURE);
            break;
        }
    }

    if (optind == argc)
    {
        usage(argv[0]);
        exit(EXIT_FAILURE);
    }
    
    elf_info.file_name = argv[optind];
}

static void print_error(const char *format, ...)
{
    va_list ap;

    va_start(ap, format);
    vfprintf(stderr, format, ap);
    va_end(ap);

    fflush(stderr);
}

void hex_dump(const void *buff, const int count)
{
#define DISPLAY_BYTE_PER_LINE 16

    char buff_byte[256];
    char buff_ascii[256];
    char buff_temp[3 + 1];
    int j = 0;

    memset(buff_byte, 0, sizeof(buff_byte));
    memset(buff_ascii, 0, sizeof(buff_ascii));

    for (int i = 0; i < count; ++i)
    {
        snprintf(buff_temp, sizeof(buff_temp), "%2.2X ",
                 ((unsigned char *)buff)[i]);
        strncat(buff_byte, buff_temp, sizeof(buff_byte) - 1);
        if (isprint(((char *)buff)[i]))
        {
            strncat(buff_ascii, (char *)buff + i, 1);
        }
        else
        {
            strncat(buff_ascii, ".", 1);
        }

        if (++j == DISPLAY_BYTE_PER_LINE || i == count - 1)
        {
            printf("%-*s | %-*s |\n",
                   DISPLAY_BYTE_PER_LINE * 3, buff_byte,
                   DISPLAY_BYTE_PER_LINE, buff_ascii);
            memset(buff_byte, 0, sizeof(buff_byte));
            memset(buff_ascii, 0, sizeof(buff_ascii));
            j = 0;
        }
    }

#undef DISPLAY_BYTE_PER_LINE
}

static int process_file(FILE *fp)
{
    int ret = 0;

    if (read_elf_header(fp) != 0)
        return -1;

    /* alloc elf_section_info_table */
    if (NULL == (elf_section_info_table =
                (elf_section_info_t *)malloc(sizeof(elf_section_info_t) *
                                             elf_info.section_header_count)))
    {
        MALLOC_ERROR();
        goto exit_point;
    }

    if (read_section_header(fp) != 0)
    {
        ret = -1;
        goto exit_point;
    }

    /* alloc section_name_table */
    elf_info.section_name_table_size =
        elf_section_info_table[elf_info.section_name_table_index].size;
    if (NULL == (elf_info.section_name_table =
                 (char *)malloc(elf_info.section_name_table_size)))
    {
        MALLOC_ERROR();
        ret = -1;
        goto exit_point;
    }

    if (read_section_name_table(fp) != 0)
    {
        ret = -1;
        goto exit_point;
    }

    print_info(fp);

exit_point:
    free(elf_section_info_table);
    free(elf_info.section_name_table);

    return ret;
}

static int read_elf_header(FILE *fp)
{
    unsigned char ident[EI_NIDENT];
    Elf32_Ehdr elf32_header;
    Elf64_Ehdr elf64_header;

    if (fread(ident, sizeof(ident), 1, fp) != 1)
    {
        FREAD_ERROR();
        return -1;
    }

    if (ident[EI_MAG0] != ELFMAG0 ||
        ident[EI_MAG1] != ELFMAG1 ||
        ident[EI_MAG2] != ELFMAG2)
    {
        print_error("file is not ELF format\n");
        return -1;
    }

    elf_info.class = ident[EI_CLASS];

    if (elf_info.class != ELFCLASS32 &&
        elf_info.class != ELFCLASS64)
    {
        print_error("invalid elf class[%d]\n", elf_info.class);
        return -1;
    }

    rewind(fp);

    if (ELFCLASS32 == elf_info.class)
    {
        if (fread(&elf32_header, sizeof(elf32_header), 1, fp) != 1)
        {
            FREAD_ERROR();
            return -1;
        }
        elf_info.type = elf32_header.e_type;
        elf_info.entry_point = elf32_header.e_entry;
        elf_info.program_header_table_offset = elf32_header.e_phoff;
        elf_info.program_header_count = elf32_header.e_phnum;
        elf_info.section_header_table_offset = elf32_header.e_shoff;
        elf_info.section_header_count = elf32_header.e_shnum;
        elf_info.section_name_table_index = elf32_header.e_shstrndx;
    }
    else if (ELFCLASS64 == elf_info.class)
    {
        if (fread(&elf64_header, sizeof(elf64_header), 1, fp) != 1)
        {
            FREAD_ERROR();
            return -1;
        }
        elf_info.type = elf64_header.e_type;
        elf_info.entry_point = elf64_header.e_entry;
        elf_info.program_header_table_offset = elf64_header.e_phoff;
        elf_info.program_header_count = elf64_header.e_phnum;
        elf_info.section_header_table_offset = elf64_header.e_shoff;
        elf_info.section_header_count = elf64_header.e_shnum;
        elf_info.section_name_table_index = elf64_header.e_shstrndx;
    }

    return 0;
}

static int read_section_header(FILE *fp)
{
    Elf32_Shdr elf32_section_header;
    Elf64_Shdr elf64_section_header;

    rewind(fp);
    if (fseek(fp, elf_info.section_header_table_offset, SEEK_SET) != 0)
    {
        FSEEK_ERROR();
        return -1;
    }

    if (ELFCLASS32 == elf_info.class)
    {
        for (int i = 0; i < elf_info.section_header_count; ++i)
        {
            if (fread(&elf32_section_header, sizeof(Elf32_Shdr), 1, fp) != 1)
            {
                FREAD_ERROR();
                return -1;
            }
            elf_section_info_table[i].name_index = elf32_section_header.sh_name;
            elf_section_info_table[i].offset = elf32_section_header.sh_offset; 
            elf_section_info_table[i].size = elf32_section_header.sh_size;
            elf_section_info_table[i].address = elf32_section_header.sh_addr;
        }
    }
    else if (ELFCLASS64 == elf_info.class)
    {
        for (int i = 0; i < elf_info.section_header_count; ++i)
        {
            if (fread(&elf64_section_header, sizeof(Elf64_Shdr), 1, fp) != 1)
            {
                FREAD_ERROR();
                return -1;
            }
            elf_section_info_table[i].name_index = elf64_section_header.sh_name;
            elf_section_info_table[i].offset = elf64_section_header.sh_offset; 
            elf_section_info_table[i].size = elf64_section_header.sh_size;
            elf_section_info_table[i].address = elf64_section_header.sh_addr;
        }
    }

    return 0;
}

static int read_section_name_table(FILE *fp)
{
    rewind(fp);
    if (fseek(fp, elf_section_info_table
                  [elf_info.section_name_table_index].offset,
              SEEK_SET) != 0)
    {
        FSEEK_ERROR();
        return -1;
    }

    if (fread(elf_info.section_name_table,
              elf_info.section_name_table_size, 1, fp) != 1)
    {
        FREAD_ERROR();
        return -1;
    }

    return 0;
}

static int print_info(FILE *fp)
{
    int width = (ELFCLASS32 == elf_info.class) ? 8 : 16;

    if (do_section_dump)
    {
        if (dump_section(fp, argv_section_dump_num) != 0)
        {
            return -1;
        }
        return 0;
    }

    printf("%s  Type: %s  Entry Point: 0x%8.8zx\n",
           describe_elf_class(elf_info.class),
           describe_elf_type(elf_info.type),
           elf_info.entry_point);
    printf("Program Header Offset: 0x%*.*zx, Entry Count: %3zd\n",
           width, width, elf_info.program_header_table_offset,
           elf_info.program_header_count);
    printf("Section Header Offset: 0x%*.*zx, Entry Count: %3zd\n",
           width, width, elf_info.section_header_table_offset,
           elf_info.section_header_count);

    for (size_t i = 0; i < elf_info.section_header_count; ++i)
    {
        printf("[%3zd]%-20.20s Offset: 0x%*.*zx Size: 0x%*.*zx Addr: 0x%*.*zx\n", i,
               elf_info.section_name_table + elf_section_info_table[i].name_index,
               width, width, elf_section_info_table[i].offset,
               width, width, elf_section_info_table[i].size,
               width, width, elf_section_info_table[i].address);
    }

    return 0;
}

static int dump_section(FILE *fp, const size_t section_num)
{
    void *dump_buff = NULL;

    if (section_num > elf_info.section_header_count - 1)
    {
        print_error("invalid section num[%3zd]\n", section_num);
        return -1;
    }
    size_t offset = elf_section_info_table[section_num].offset;
    size_t size = elf_section_info_table[section_num].size;

    rewind(fp);
    if (fseek(fp, offset, SEEK_SET) != 0)
    {
        FSEEK_ERROR();
        return -1;
    }

    if (NULL == (dump_buff = malloc(size)))
    {
        MALLOC_ERROR();
        return -1;
    }

    if (fread(dump_buff, size, 1, fp) != 1)
    {
        free(dump_buff);
        FREAD_ERROR();
        return -1;
    }

    printf("Section %3zd Dump:\n", section_num);
    hex_dump(dump_buff, size);

    free(dump_buff);

    return 0;
}

static const char *describe_elf_type(const int elf_type)
{
    switch (elf_type)
    {
    case ET_NONE:
        return "No file type";
    case ET_REL:
        return "Relocatable file";
    case ET_EXEC:
        return "Executable file";
    case ET_DYN:
        return "Shared object file";
    case ET_CORE:
        return "Core file";
    default:
        return "Unknown";
    }
}

static const char *describe_elf_class(const int elf_class)
{
    switch (elf_class)
    {
    case ELFCLASSNONE:
        return "Invalid class";
    case ELFCLASS32:
        return "ELF-32bit";
    case ELFCLASS64:
        return "ELF-64bit";
    default:
        return "Unknown";
    }
}
